;***************************************************************************
; Apollo 0.5 BBS bootloader MBR portion
; Author: Aidan Goddard 14/5/14 
; 
; boot.asm
; finds and loads /BOOT/BBSBoot.sys to 0x8000
; then transfers execution to it
; BBSBoot.sys has a maximum size of 32kb
;***************************************************************************
[BITS 16]
[ORG 0x7c00]
jmp	0x0000:START

;***************************************************************************
; main start
; set segment registers
START:
	mov		ax, cs
	mov		ds, ax 
	mov		es, ax

	; set stack to be just below where this is loaded to
	mov		ss, ax
	mov		sp, 0x7c00

	; save drive number
	mov		[DriveNumber], dl

	; clear the screen & set the video mode
	mov		ax, 0x0003
	int		0x10
	
	; load system/boot/bootbbs.sys to 0x0000:0x8000 (0x8000)
	mov		si, BootFileName
	mov		bx, 0x0000
	mov		di, 0x8000
	call	LoadFile

	; jump to it if no error
	jnc		0x8000

	; print error string
	mov		si, NoFileError
	call	PrintString
	cli
	hlt

;***************************************************************************
; functions

; function to load a file from the given root cluster
; the root cluster number will point to the cluster of
; whereever the boot files are located
; si = file name
; bx:di = destination

LoadFile:
	; save the file name & destination
	push	bx
	push	di
	push	si

	; get root cluster number
	mov		eax, [FatRootCluster]

.load_search_cluster:
	; eax = cluster to scan
	push	eax				; save cluster number
	xor		bx, bx			; clear destination segment
	mov		di, 0x1000		; set destination offset
	call	ReadCluster		; call the function

	; set up for loop
	mov		di, 0x1000		; base of cluster
	mov		si, sp			; get stack ptr
	mov		si, [si + 4]	; get file name
	xor		dx, dx			; clear counter

.search_cluster_dirs:
	; check names in this cluster
	mov		cx, 11			; file name length
	call	CompString

	jnc		.found_file_entry


	; not this entry
	add		di, 32
	add		dx, 32
	cmp		dx, WORD [FatClusterSize]
	jne		.search_cluster_dirs

	; need to load the next cluster
	pop		eax						; retrieve cluster number
	call	GetNextCluster			; get the next cluster number
	jnc		.load_search_cluster	; get the next one to load

	add		sp, 6					; balance stack
	stc
	ret								; exit on error, or end of chain

.found_file_entry:
	; balance the stack
	add		sp, 6

	; di points to this entry's base
	; get start cluster number
	mov		ax, [di + 20]
	shl		eax, 16
	mov		ax, [di + 26]
	
	; get destination
	pop		di
	pop		bx

.load_loop:
	; bx:di = destination for file
	; eax = cluster to load
	; save them
	push	bx
	push	di
	push	eax

	; load the cluster
	call	ReadCluster

	; restore cluster number & get next cluster
	pop		eax
	call	GetNextCluster
	jc		.finish

	; increase pointers
	pop		di							; retrieve offset
	pop		bx							; retrieve segment
	add		di, WORD [FatClusterSize]	; add cluster size to offset
	jmp		.load_loop					; loop


.finish:
	pop		di
	pop		bx
	clc
	ret



;***************************************************************************
; function to read a cluster
; eax = cluster number
; bx:di = destination
; sets carry on error

ReadCluster:
	; need to calculate start sector number
	sub		eax, 2									; subtract 2 from cluster number
	mov		ecx, [Fat512SectorsPerCluster]			; get number of sectors per cluster
	mul		ecx										; multiply the above together
	add		eax, DWORD [FatDataAreaStartSector]		; add offset to start of data area

	; read the sectors in
	call	ReadSectors
	jnc		.finish

.error:
	stc
	ret

.finish:
	ret

;***************************************************************************
; function to get next cluster in chain
; loads sectors to 0x7e00
; eax = current cluster
; returns eax = next cluster
; sets carry if no more clusters in chain

GetNextCluster:
	; first calculate which sector offset to load (from base of FAT table)
	; and the DWORD index into that sector that contains the next cluster number
	xor		edx, edx		; clear top 64 bits
	mov		ecx, 128		; divisor (128 4-byte enties in a 512-byte sector)
	div		ecx				; perform division

	; eax = sector number
	; edx = entry number
	add		eax, [FatTableStartSector]	; add the base address of the FAT table start
	push	dx							; save the entry number
	mov		cx, 1						; want 1 sector
	xor		bx, bx						; destination segment 0x0000
	mov		di, 0x7e00					; destination offset 0x7e00
	call	ReadSectors					; call the function
	pop		di							; restore entry number
	jc		.error						; error out

	; now read the value and return it, if valid
	shl		di, 2						; multiply index by 4
	add		di, 0x7e00					; add offset
	mov		eax, [di]					; get the value
	and		eax, 0x0fffffff				; mask top 4 bits
	cmp		eax, 0x0fffffef				; check for end of file
	jge		.error						; end of chain, error out
	or		eax, eax					; check for zero
	jz		.error						; error out if zero
	cmp		eax, 0x0ffffff7				; check for error
	jb		.finish						; if is fine, return it. otherwise error

.error:
	stc
	ret

.finish:
	clc
	ret

;***************************************************************************
; function to read sectors from drive
; eax = sector start
; cx = sector count
; bx = destination segment
; di = destination offset

; DAP
DAP:
.size		db 16
.zero		db 0
.count		dw 0
.offset		dw 0
.segment	dw 0
.sector		dq 0

; fuction
ReadSectors:
	; save parameters to the DAP
	mov		[DAP.count], cx
	mov		[DAP.segment], bx
	mov		[DAP.offset], di
	mov		[DAP.sector], eax

	; setup for drive reset (5 attempts)
.reset_loop:
	mov		cx, 5				; 5 attempts
	xor		ah, ah				; reset disk system command
	mov		dl, [DriveNumber]	; get disk drive number
	int		0x13				; call BIOS
	jnc		.reset_success		; leave if reset was success
	loop	.reset_loop			; try again if not
	jmp		.error				; if loop fell through, could not reset the disk

	; drive was successfully reset
.reset_success:
	; attempt to load the data 5 times
	mov		cx, 5				; 5 attempts
	mov		ah, 0x42			; extended read mode
	mov		si, DAP				; pass it the address of the DAP
	int		0x13				; call BIOS
	jnc		.finish				; leave if success
	loop	.reset_success		; loop otherwise

.error:
	stc
	ret

.finish:
	ret

;***************************************************************************
; compare 2 strings
; si = ptr to string 1
; di = ptr to string 2
; cx = length of strings
; sets carry on fail

CompString:
	; si and di point to the chars
	; save di & si
	push	di
	push	si

.loop:
	; check the char
	mov		al, [si]
	mov		bl, [di]
	inc		si
	inc		di
	cmp		al, bl
	jne		.fail
	loop	.loop

.success:
	pop		si
	pop		di
	clc
	ret

.fail:
	pop		si
	pop		di
	stc
	ret

;***************************************************************************
; print a string
; si = pointer to string to print

PrintString:
	; setup registers for video out mode
	mov		ah, 0x0e
	mov		bh, 0x00
	mov		bl, 0x07

.loop:
	; get char
	lodsb		; same as "mov	al, [si] + inc	si"

	; check for nullchar
	or		al, al
	jz		.finish

	; if not null, print it & repeat
	int		0x10
	jmp		.loop

.finish:
	mov		al, ':'
	int		0x10
	ret


;****************************************************************************
; data
BootFileName	db "BOOTBBS SYS",0 
NoFileError		db "Error: no boot file",0 

;***************************************************************************
; Apollo 0.5 bootloader data table
times 400 - ($ - $$) db 0

FatTableStartSector			dd 0
FatDataAreaStartSector		dd 0
FatClusterSize				dd 0
Fat512SectorsPerCluster		dd 0
FatRootCluster				dd 0
reserved					dd 0, 0
DriveNumber					dd 0
OSSignature					dd 0x352e3041	; "A0.5"
PartitionScheme				dd 0			; 0 = MBR, 1 = GPT
BootPartitionStartSector	dd 0
BootloaderVersionNumber		dw 0

;***************************************************************************
; bootsig
times 446 - ($ - $$) db 0
times 510 - ($ - $$) db 0
dw 0xAA55
